#include "gpio.hpp"
#include "Pin.hpp"
#include "system.hpp"

static const int external_interrupt_channel_count = 16;

struct ExternalInterruptResource {
    Pin *instance;
    Pin::EventCallback rising_edge_callback;
    Pin::EventCallback falling_edge_callback;
} external_interrupt_resources[external_interrupt_channel_count] = { 
    { .instance = nullptr, .rising_edge_callback = nullptr, .falling_edge_callback = nullptr, },
};

struct PortResource {
    Port *instance;
    GPIO_Register_t *reg;
    System::ClockChannel clock_channel;
    int width;
} port_resources[] = {
    [0] = {
        .instance = nullptr,
        .reg = (GPIO_Register_t*)0x40010800,
        .clock_channel = System::ClockChannel::GPIOA,
        .width = 16,
    },
    [1] = {
        .instance = nullptr,
        .reg = (GPIO_Register_t*)0x40010c00,
        .clock_channel = System::ClockChannel::GPIOB,
        .width = 16,
    },
    [2] = {
        .instance = nullptr,
        .reg = (GPIO_Register_t*)0x40011000,
        .clock_channel = System::ClockChannel::GPIOC,
        .width = 16,
    },
    [3] = {
        .instance = nullptr,
        .reg = (GPIO_Register_t*)0x40011400,
        .clock_channel = System::ClockChannel::GPIOD,
        .width = 16,
    },
    [4] = {
        .instance = nullptr,
        .reg = (GPIO_Register_t*)0x40011800,
        .clock_channel = System::ClockChannel::GPIOE,
        .width = 16,
    },
};

class __Pin : public Pin {
public:
    __Pin(int port_number,int pin_number);
    virtual ~__Pin();
    virtual bool open(void);
    virtual bool close(void);
    virtual void set(bool status);
    virtual bool get(void);

    virtual bool setMode(Mode mode);
    virtual Mode getMode(void);
    virtual bool setDriveType(DriveType drive_type);
    virtual DriveType getDriveType(void);
    virtual bool setPullType(PullType pull_type);
    virtual PullType getPullType(void);

    virtual bool registEvent(EventType event_type,EventCallback event_callback);
    virtual bool unregistEvent(EventType event_type);
private:
    int port_number;
    int pin_number;
    Mode mode;
    DriveType drive_type;
    PullType pull_type;
    bool status;
};

class __Port : public Port {
public:
    __Port(int port_number);
    virtual ~__Port();
    virtual bool open(void);
    virtual bool close(void);
    virtual int read(void *buffer,int size);
    virtual int write(void *buffer,int size);
    virtual Pin *getPin(int pin_number);
private:
    int port_number;
    Pin **pins;
};

__Pin::__Pin(int port_number,int pin_number){
    this->port_number = port_number;
    this->pin_number = pin_number;
    /* Default configuration. */
    this->status = false;
    this->mode = Mode::Input;
    this->drive_type = DriveType::PushPull;
    this->pull_type = PullType::None;
}

__Pin::~__Pin(){ }

bool __Pin::open(void){
    uint32_t config_register = 0x00000000;
    if ( this->mode == Mode::Input ){
        switch ( this->pull_type ){
            case PullType::None: config_register = 0b0100; break;
            case PullType::PullUp:
                port_resources[this->port_number].reg->OUTDR |= (0x01 << this->pin_number);
                config_register = 0b1000;
                break;
            case PullType::PullDown:
                port_resources[this->port_number].reg->OUTDR &= ~(0x01 << this->pin_number);
                config_register = 0b1000;
                break;
            default: config_register = 0b0100; break;
        }
    }else{
        config_register |= 0b0011;
        if ( this->drive_type == DriveType::OpenDrain ){
            config_register |= 0b0100;
        }
    }
    if ( this->pin_number < 8 ){
        config_register <<= (this->pin_number * 4);
        port_resources[this->port_number].reg->CFGLR &= ~(0x0f << (this->pin_number * 4));
        port_resources[this->port_number].reg->CFGLR |= config_register;
        if ( (port_resources[this->port_number].reg->CFGLR & (0x0f << (this->pin_number * 4))) == config_register ){
            this->status = true;
        }else{
            this->status = false;
        }
    }else{
        config_register <<= ((this->pin_number - 8) * 4);
        port_resources[this->port_number].reg->CFGHR &= ~(0x0f << ((this->pin_number - 8) * 4));
        port_resources[this->port_number].reg->CFGHR |= config_register;
        if ( (port_resources[this->port_number].reg->CFGHR & (0x0f << ((this->pin_number - 8) * 4))) == config_register ){
            this->status = true;
        }else{
            this->status = false;
        }
    }
    return this->status;
}

bool __Pin::close(void){
    this->status = false;
    return !this->status;
}

void __Pin::set(bool status){
    if ( this->status ){
        if ( status ){
            port_resources[this->port_number].reg->BSHR = (0x01 << this->pin_number);
        }else{
            port_resources[this->port_number].reg->BCR = (0x01 << this->pin_number);
        }
    }
}

bool __Pin::get(void){
    if ( port_resources[this->port_number].reg->INDR & (0x01 << this->pin_number) ){
        return true;
    }else{
        return false;
    }
}

bool __Pin::setMode(Mode mode){
    this->mode = mode;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getMode() != mode ) return false;
    }
    return true;
}

Pin::Mode __Pin::getMode(void){
    uint32_t config_register = 0x00000000;
    if ( !this->status ) return this->mode;
    /* Update mode when pin opening. */
    if ( this->pin_number < 8 ){
        config_register = port_resources[this->port_number].reg->CFGLR;
        config_register >>= (4 * this->pin_number);
    }else{
        config_register = port_resources[this->port_number].reg->CFGHR;
        config_register >>= (4 * (this->pin_number - 8));
    }

    if ( (config_register & 0b0011) == 0x00 ){
        this->mode = Mode::Input;
    }else{
        this->mode = Mode::Output;
    }
    return this->mode;
}

bool __Pin::setDriveType(DriveType drive_type){
    this->drive_type = drive_type;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getDriveType() != drive_type ) return false;
    }
    return true;
}

Pin::DriveType __Pin::getDriveType(void){
    uint32_t config_register = 0x00000000;
    if ( !this->status || (this->getMode() != Mode::Output) ) return this->drive_type;
    if ( this->pin_number < 8 ){
        config_register = port_resources[this->port_number].reg->CFGLR;
        config_register >>= (4 * this->pin_number);
    }else{
        config_register = port_resources[this->port_number].reg->CFGHR;
        config_register >>= (4 * (this->pin_number - 8));
    }
    if ( config_register & 0b0100 ){
        this->drive_type = DriveType::OpenDrain;
    }else{
        this->drive_type = DriveType::PushPull;
    }
    return this->drive_type;
}

bool __Pin::setPullType(PullType pull_type){
    this->pull_type = pull_type;
    if ( this->status ){
        if ( !this->close() ) return false;
        if ( !this->open() ) return false;
        if ( this->getPullType() != pull_type ) return false;
    }
    return true;
}

Pin::PullType __Pin::getPullType(void){
    uint32_t config_register = 0x00000000;
    uint32_t out_data_register = 0x00000000;
    if ( !this->status || (this->getMode() != Mode::Input) ) return this->pull_type;
    if ( this->pin_number < 8 ){
        config_register = port_resources[this->port_number].reg->CFGLR;
        config_register >>= (4 * this->pin_number);
    }else{
        config_register = port_resources[this->port_number].reg->CFGHR;
        config_register >>= (4 * (this->pin_number - 8));
    }
    out_data_register = port_resources[this->port_number].reg->OUTDR;
    out_data_register >>= this->pin_number;
    if ( (config_register & 0b1000) != 0 ){
        if ( (out_data_register & 0x01) != 0 ){
            this->pull_type = PullType::PullUp;
        }else{
            this->pull_type = PullType::PullDown;
        }
    }else{
        this->pull_type = PullType::None;
    }
    return this->pull_type;
}

bool __Pin::registEvent(EventType event_type,EventCallback event_callback){
    /* Check external interrupt channel occupancy. */
    if ( external_interrupt_resources[this->pin_number].instance != nullptr && external_interrupt_resources[this->pin_number].instance != this ){
        return false;
    }
    /* Occupying the channel. */
    external_interrupt_resources[this->pin_number].instance = this;

    uint8_t type_map = (uint8_t)event_type;
    if ( (type_map & (uint8_t)EventType::RisingEdge) != 0 ) external_interrupt_resources[this->pin_number].rising_edge_callback = event_callback;
    if ( (type_map & (uint8_t)EventType::FallingEdge) != 0 ) external_interrupt_resources[this->pin_number].falling_edge_callback = event_callback;

    /* Enable external interrupt. */
    System::InterruptChannel interrupt_channel;
    switch ( this->pin_number ){
        case 0: interrupt_channel = System::InterruptChannel::EXTI0; break;
        case 1: interrupt_channel = System::InterruptChannel::EXTI1; break;
        case 2: interrupt_channel = System::InterruptChannel::EXTI2; break;
        case 3: interrupt_channel = System::InterruptChannel::EXTI3; break;
        case 4: interrupt_channel = System::InterruptChannel::EXTI4; break;
        case 5:
        case 6:
        case 7:
        case 8:
        case 9: interrupt_channel = System::InterruptChannel::EXTI9_5; break;
        case 10:
        case 11:
        case 12:
        case 13:
        case 14:
        case 15: interrupt_channel = System::InterruptChannel::EXTI15_10; break;
    }
    System::setInterruptChannelStatus(interrupt_channel,true);
    System::setClockChannelStatus(System::ClockChannel::AFIO,true);
    int remap_reg_index = this->pin_number / 4;
    int remap_reg_offset = this->pin_number % 4;
    AFIO_register->EXTICR[remap_reg_index] &= ~(0x0f << (4 * remap_reg_offset));
    AFIO_register->EXTICR[remap_reg_index] |= ((this->port_number & 0x0f) << (4 * remap_reg_offset));

    if ( (type_map & (uint8_t)EventType::RisingEdge) != 0 ) EXTI_register->RTENR |= (0x01 << this->pin_number);
    if ( (type_map & (uint8_t)EventType::FallingEdge) != 0 ) EXTI_register->FTENR |= (0x01 << this->pin_number);
    EXTI_register->INTENR |= (0x01 << this->pin_number);

    return true;
}

bool __Pin::unregistEvent(EventType event_type){
    /* Check external interrupt channel occupancy. */
    if ( external_interrupt_resources[this->pin_number].instance != this ){
        return false;
    }
    
    /* Unregist event callback. */
    uint8_t type_map = (uint8_t)event_type;
    if ( (type_map & (uint8_t)EventType::RisingEdge) != 0 ) external_interrupt_resources[this->pin_number].rising_edge_callback = nullptr;
    if ( (type_map & (uint8_t)EventType::FallingEdge) != 0 ) external_interrupt_resources[this->pin_number].falling_edge_callback = nullptr;

    /* Close external interrupt if no callback function has been regist. */
    if ( external_interrupt_resources[this->pin_number].rising_edge_callback == nullptr && external_interrupt_resources[this->pin_number].falling_edge_callback == nullptr ){
        EXTI_register->INTENR &= ~(0x01 << this->pin_number);
        external_interrupt_resources[this->pin_number].instance = nullptr;
    }

    return true;
}

__Port::__Port(int port_number){
    this->port_number = port_number;
    this->pins = new Pin*[port_resources[this->port_number].width];
    for ( int index = 0; index < port_resources[this->port_number].width; index++ ){
        this->pins[index] = nullptr;
    }
}

__Port::~__Port(){
    if ( this->pins != nullptr ){
        for ( int index = 0; index < port_resources[this->port_number].width; index++ ){
            if ( this->pins[index] != nullptr ){
                this->pins[index]->close();
                delete this->pins[index];
                this->pins[index] = nullptr;
            }
        }
        delete[] this->pins;
        this->pins = nullptr;
    }
}

bool __Port::open(void){
    if ( !System::setClockChannelStatus(port_resources[this->port_number].clock_channel,true) ) return false;
    return true;
}

bool __Port::close(void){
    return true;
}

int __Port::read(void *buffer,int size){
    unsigned char *uc_buffer = (unsigned char*)buffer;
    uint32_t input_data_reg = port_resources[this->port_number].reg->INDR;
    int port_size = ((port_resources[this->port_number].width % 8) == 0) ? (port_resources[this->port_number].width / 8) : (port_resources[this->port_number].width % 8 + 1);
    if ( size > port_size ) size = port_size;
    for ( int n = size; n > 0; n-- ){
        uc_buffer[n] = (input_data_reg & 0xff);
        input_data_reg >>= 8;
    }
    return size;
}

int __Port::write(void *buffer,int size){
    unsigned char *uc_buffer = (unsigned char*)buffer;
    uint32_t output_data_reg = 0x00000000;
    int port_size = ((port_resources[this->port_number].width % 8) == 0) ? (port_resources[this->port_number].width / 8) : (port_resources[this->port_number].width % 8 + 1);
    if ( size > port_size ) size = port_size;
    for ( int n = size; n > 0; n-- ){
        output_data_reg |= uc_buffer[n];
        output_data_reg <<= 8;
    }
    port_resources[this->port_number].reg->OUTDR = output_data_reg;
    return size;
}

Pin *__Port::getPin(int pin_number){
    if ( pin_number >= port_resources[this->port_number].width ) return nullptr;
    if ( this->pins[pin_number] == nullptr ){
        this->pins[pin_number] = new __Pin(this->port_number,pin_number);
    }
    return this->pins[pin_number];
}

::Port *CH32V30X::Port::getInstance(int port_number){
    if ( port_number >= (sizeof(port_resources) / sizeof(PortResource)) ) return nullptr;
    if ( port_resources[port_number].instance == nullptr ){
        port_resources[port_number].instance = new __Port(port_number);
    }
    return port_resources[port_number].instance;
}

bool CH32V30X::Port::freeInstance(::Port *instance){
    for ( int index = 0; index < (sizeof(port_resources) / sizeof(PortResource)); index++ ){
        if ( port_resources[index].instance == instance ){
            if ( !port_resources[index].instance->close() ) return false;
            delete port_resources[index].instance;
            port_resources[index].instance = nullptr;
            return true;
        }
    }
    return false;
}

extern "C" {

__attribute__((interrupt()))
void EXTI0_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( (EXTI_register->INTFR & (0x01 << 0)) != 0 ){
        if ( external_interrupt_resources[0].instance->get() ){
            /* Rising edge. */
            if ( external_interrupt_resources[0].rising_edge_callback != nullptr ){
                external_interrupt_resources[0].rising_edge_callback();
            }
        }else{
            /* Falling edit. */
            if ( external_interrupt_resources[0].falling_edge_callback != nullptr ){
                external_interrupt_resources[0].falling_edge_callback();
            }
        }
        /* Clear interrupt pending bit. */
        EXTI_register->INTFR |= (0x01 << 0);
    }
}

__attribute__((interrupt()))
void EXTI1_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( PFIC_register->IPR[((uint32_t)System::InterruptChannel::EXTI1) >> 5] & (0x01 << (((uint32_t)System::InterruptChannel::EXTI1) & 0x1f)) != 0 ){
        if ( external_interrupt_resources[1].instance->get() ){
            /* Rising edge. */
            if ( external_interrupt_resources[1].rising_edge_callback != nullptr ){
                external_interrupt_resources[1].rising_edge_callback();
            }
        }else{
            /* Falling edit. */
            if ( external_interrupt_resources[1].falling_edge_callback != nullptr ){
                external_interrupt_resources[1].falling_edge_callback();
            }
        }
        /* Clear interrupt pending bit. */
        PFIC_register->IPRR[((uint32_t)System::InterruptChannel::EXTI1) >> 5] = (0x01 << (((uint32_t)System::InterruptChannel::EXTI1) & 0x1f));
    }
}

__attribute__((interrupt()))
void EXTI2_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( PFIC_register->IPR[((uint32_t)System::InterruptChannel::EXTI2) >> 5] & (0x01 << (((uint32_t)System::InterruptChannel::EXTI2) & 0x1f)) != 0 ){
        if ( external_interrupt_resources[2].instance->get() ){
            /* Rising edge. */
            if ( external_interrupt_resources[2].rising_edge_callback != nullptr ){
                external_interrupt_resources[2].rising_edge_callback();
            }
        }else{
            /* Falling edit. */
            if ( external_interrupt_resources[2].falling_edge_callback != nullptr ){
                external_interrupt_resources[2].falling_edge_callback();
            }
        }
        /* Clear interrupt pending bit. */
        PFIC_register->IPRR[((uint32_t)System::InterruptChannel::EXTI2) >> 5] = (0x01 << (((uint32_t)System::InterruptChannel::EXTI2) & 0x1f));
    }
}

__attribute__((interrupt()))
void EXTI3_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( PFIC_register->IPR[((uint32_t)System::InterruptChannel::EXTI3) >> 5] & (0x01 << (((uint32_t)System::InterruptChannel::EXTI3) & 0x1f)) != 0 ){
        if ( external_interrupt_resources[3].instance->get() ){
            /* Rising edge. */
            if ( external_interrupt_resources[3].rising_edge_callback != nullptr ){
                external_interrupt_resources[3].rising_edge_callback();
            }
        }else{
            /* Falling edit. */
            if ( external_interrupt_resources[3].falling_edge_callback != nullptr ){
                external_interrupt_resources[3].falling_edge_callback();
            }
        }
        /* Clear interrupt pending bit. */
        PFIC_register->IPRR[((uint32_t)System::InterruptChannel::EXTI3) >> 5] = (0x01 << (((uint32_t)System::InterruptChannel::EXTI3) & 0x1f));
    }
}

__attribute__((interrupt()))
void EXTI4_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( PFIC_register->IPR[((uint32_t)System::InterruptChannel::EXTI4) >> 5] & (0x01 << (((uint32_t)System::InterruptChannel::EXTI4) & 0x1f)) != 0 ){
        if ( external_interrupt_resources[4].instance->get() ){
            /* Rising edge. */
            if ( external_interrupt_resources[4].rising_edge_callback != nullptr ){
                external_interrupt_resources[4].rising_edge_callback();
            }
        }else{
            /* Falling edit. */
            if ( external_interrupt_resources[4].falling_edge_callback != nullptr ){
                external_interrupt_resources[4].falling_edge_callback();
            }
        }
        /* Clear interrupt pending bit. */
        PFIC_register->IPRR[((uint32_t)System::InterruptChannel::EXTI4) >> 5] = (0x01 << (((uint32_t)System::InterruptChannel::EXTI4) & 0x1f));
    }
}

__attribute__((interrupt()))
void EXTI9_5_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( PFIC_register->IPR[((uint32_t)System::InterruptChannel::EXTI9_5) >> 5] & (0x01 << (((uint32_t)System::InterruptChannel::EXTI9_5) & 0x1f)) != 0 ){
        for ( int index = 5; index <= 9; index++ ){
            /* Check interrupt flag bit. */
            if ( (EXTI_register->INTFR & (0x01 << index)) != 0 ){
                if ( external_interrupt_resources[index].instance->get() ){
                    /* Rising edge. */
                    if ( external_interrupt_resources[index].rising_edge_callback != nullptr ){
                        external_interrupt_resources[index].rising_edge_callback();
                    }
                }else{
                    /* Falling edit. */
                    if ( external_interrupt_resources[index].falling_edge_callback != nullptr ){
                        external_interrupt_resources[index].falling_edge_callback();
                    }
                }
                EXTI_register->INTFR |= (0x01 << index);
            }
        }
        
        /* Clear interrupt pending bit. */
        PFIC_register->IPRR[((uint32_t)System::InterruptChannel::EXTI9_5) >> 5] = (0x01 << (((uint32_t)System::InterruptChannel::EXTI9_5) & 0x1f));
    }
}

__attribute__((interrupt()))
void EXTI15_10_IRQHandler(void){
    /* Check interrupt pending bit. */
    if ( PFIC_register->IPR[((uint32_t)System::InterruptChannel::EXTI15_10) >> 5] & (0x01 << (((uint32_t)System::InterruptChannel::EXTI15_10) & 0x1f)) != 0 ){
        for ( int index = 10; index <= 15; index++ ){
            /* Check interrupt flag bit. */
            if ( (EXTI_register->INTFR & (0x01 << index)) != 0 ){
                if ( external_interrupt_resources[index].instance->get() ){
                    /* Rising edge. */
                    if ( external_interrupt_resources[index].rising_edge_callback != nullptr ){
                        external_interrupt_resources[index].rising_edge_callback();
                    }
                }else{
                    /* Falling edit. */
                    if ( external_interrupt_resources[index].falling_edge_callback != nullptr ){
                        external_interrupt_resources[index].falling_edge_callback();
                    }
                }
                EXTI_register->INTFR |= (0x01 << index);
            }
        }
        
        /* Clear interrupt pending bit. */
        PFIC_register->IPRR[((uint32_t)System::InterruptChannel::EXTI15_10) >> 5] = (0x01 << (((uint32_t)System::InterruptChannel::EXTI15_10) & 0x1f));
    }
}

}